package file.handling;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/** Utility class to simplify reading and writing XML DOM */
public class XMLUtil
{

   /**
    * Creates a new XML document.
    * 
    * @param namespace
    *           the default namespace of the document
    * @param documentElement
    *           the name of the document element
    * @return a new XML document
    */
   public static Document createDocument(String namespace,
         String documentElement)
   {
      try
      {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         DocumentBuilder builder = factory.newDocumentBuilder();
         DOMImplementation impl = builder.getDOMImplementation();
         return impl.createDocument(namespace, documentElement, null);
      }
      catch (ParserConfigurationException e)
      {
         // This exception is never thrown, treat as fatal if it is
         throw new RuntimeException(e);
      }
   }

   /**
    * Read an XML file into a DOM tree
    * 
    * @param file
    *           the file to read
    * @return a new XML document
    * @throws SAXException
    *            if an XML parse error occurs
    * @throws IOException
    *            if a file I/O error occurs
    */
   public static Document read(File file) throws SAXException, IOException
   {
      try
      {
         DocumentBuilderFactory builderFactory = DocumentBuilderFactory
               .newInstance();
         DocumentBuilder builder = builderFactory.newDocumentBuilder();

         // Workaround for missing external DTD. When the parser looks up
         // an external reference, we provide an empty document back. While
         // not correct, we don't expect SVG files loaded by this program
         // to depend on externally defined entities; nor do we require
         // validation.
         //
         // http://stackoverflow.com/questions/2640825/how-to-parse-a-xhtml-ignoring-the-doctype-declaration-using-dom-parser
         builder.setEntityResolver(new EntityResolver()
         {
            @Override
            public InputSource resolveEntity(String publicId, String systemId)
                  throws SAXException, IOException
                  {
               return new InputSource(new StringReader(""));
                  }
         });

         return builder.parse(file);
      }
      catch (ParserConfigurationException e)
      {
         // This exception is never thrown, treat as fatal if it is
         throw new RuntimeException(e);
      }
   }

   /**
    * Write an XML DOM tree to a file
    * 
    * @param doc
    *           the document to write
    * @param file
    *           the file to write to
    * @throws IOException
    *            if a file I/O error occurs
    */
   public static void write(Document doc, File file) throws IOException
   {
      try
      {
         DOMSource domSource = new DOMSource(doc);
         FileOutputStream stream = new FileOutputStream(file);

         // OutputStreamWriter works around indent bug 6296446 in JDK
         // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446
         StreamResult streamResult = new StreamResult(new OutputStreamWriter(
               stream));
         TransformerFactory tf = TransformerFactory.newInstance();
         tf.setAttribute("indent-number", new Integer(2));
         Transformer serializer = tf.newTransformer();
         serializer.setOutputProperty(OutputKeys.INDENT, "yes");
         serializer.transform(domSource, streamResult);
      }
      catch (TransformerException e)
      {
         // This exception is never thrown, treat as fatal if it is
         throw new RuntimeException(e);
      }
   }

}
